package com.tsfyun.scm.service.impl.system;

import cn.hutool.core.collection.CollectionUtil;
import com.google.common.collect.Lists;
import com.tsfyun.common.base.config.OrikaBeanMapper;
import com.tsfyun.common.base.enums.DistributedLockEnum;
import com.tsfyun.common.base.enums.SerialClearEnum;
import com.tsfyun.common.base.enums.SerialNumberTypeEnum;
import com.tsfyun.common.base.enums.TimeRuleEnum;
import com.tsfyun.common.base.exception.ClientException;
import com.tsfyun.common.base.exception.ServiceException;
import com.tsfyun.common.base.security.SecurityUtil;
import com.tsfyun.common.base.util.DateUtils;
import com.tsfyun.common.base.util.StringUtils;
import com.tsfyun.common.base.util.TsfPreconditions;
import com.tsfyun.scm.dto.system.SerialNumberDTO;
import com.tsfyun.scm.entity.system.SerialNumber;
import com.tsfyun.scm.service.system.ISerialNumberService;
import com.tsfyun.common.base.extension.ServiceImpl;
import com.tsfyun.scm.vo.system.SerialNumberVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.redis.util.RedisLockRegistry;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.text.DecimalFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 *
 * @since 2020-03-16
 */
@Slf4j
@Service
public class SerialNumberServiceImpl extends ServiceImpl<SerialNumber> implements ISerialNumberService {

    @Autowired
    private RedisLockRegistry redisLockRegistry;

    @Autowired
    private OrikaBeanMapper beanMapper;

    @Override
    public List<SerialNumberVO> allList() {
        List<SerialNumber> serialNumberList = super.list();
        List<SerialNumberVO> serialNumberVOList = Lists.newArrayList();
        if(CollectionUtil.isNotEmpty(serialNumberList)) {
            serialNumberList.stream().forEach(sn ->{
                SerialNumberVO serialNumberVO = beanMapper.map(sn,SerialNumberVO.class);
                //单据类型
                SerialNumberTypeEnum serialNumberTypeEnum = SerialNumberTypeEnum.of(sn.getId());
                serialNumberVO.setName(Objects.nonNull(serialNumberTypeEnum)?serialNumberTypeEnum.getName():null);
                //日期规则
                TimeRuleEnum timeRuleEnum = TimeRuleEnum.of(sn.getTimeRule());
                serialNumberVO.setTimeRuleName(Objects.nonNull(timeRuleEnum)?timeRuleEnum.getName():null);
                //流水号重置方式
                SerialClearEnum serialClearEnum = SerialClearEnum.of(sn.getSerialClear());
                serialNumberVO.setSerialClearName(Objects.nonNull(serialClearEnum)?serialClearEnum.getName():null);
                //结果
                serialNumberVO.setResult(showResult(sn));
                serialNumberVOList.add(serialNumberVO);
            });
        }
        return serialNumberVOList;
    }

    @Override
    public SerialNumberVO detail(String id) {
        SerialNumber sn = super.getById(id);
        TsfPreconditions.checkArgument(Objects.nonNull(sn),new ServiceException("单据规则不存在"));

        SerialNumberVO serialNumberVO = beanMapper.map(sn,SerialNumberVO.class);
        //单据类型
        SerialNumberTypeEnum serialNumberTypeEnum = SerialNumberTypeEnum.of(sn.getId());
        serialNumberVO.setName(Objects.nonNull(serialNumberTypeEnum)?serialNumberTypeEnum.getName():null);
        //日期规则
        TimeRuleEnum timeRuleEnum = TimeRuleEnum.of(sn.getTimeRule());
        serialNumberVO.setTimeRuleName(Objects.nonNull(timeRuleEnum)?timeRuleEnum.getName():null);
        //流水号重置方式
        SerialClearEnum serialClearEnum = SerialClearEnum.of(sn.getSerialClear());
        serialNumberVO.setSerialClearName(Objects.nonNull(serialClearEnum)?serialClearEnum.getName():null);
        return serialNumberVO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void edit(SerialNumberDTO dto) {
        SerialNumber sn = super.getById(dto.getId());
        TsfPreconditions.checkArgument(Objects.nonNull(sn),new ServiceException("单据规则不存在"));
        sn.setPrefix(dto.getPrefix());
        if(StringUtils.isNotEmpty(sn.getPrefix())&&dto.getPrefix().indexOf("_")!=-1){
            throw new ServiceException("单据前缀不能包含 _ 符号");
        }
        TimeRuleEnum timeRuleEnum = TimeRuleEnum.of(dto.getTimeRule());
        TsfPreconditions.checkArgument(Objects.nonNull(timeRuleEnum),new ServiceException("日期规则不存在"));
        sn.setTimeRule(timeRuleEnum.getCode());
        sn.setSerialLength(dto.getSerialLength());
        SerialClearEnum serialClearEnum = SerialClearEnum.of(dto.getSerialClear());
        TsfPreconditions.checkArgument(Objects.nonNull(serialClearEnum),new ServiceException("流水号重置方式不存在"));
        sn.setSerialClear(serialClearEnum.getCode());

        sn.setUpdateBy(SecurityUtil.getCurrentPersonIdAndName());
        sn.setDateUpdated(LocalDateTime.now());
        super.updateById(sn);
    }

    @Override
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
    public String generateDocNo(SerialNumberTypeEnum serialNumberTypeEnum) {
        String serialNumber = serialNumberTypeEnum.getCode().intern();
        //此处再加锁，防止系统服务取单号重复
        Lock lock = redisLockRegistry.obtain(DistributedLockEnum.GEN_DOC_NO.getCode() + ":" + serialNumber);
        boolean isLock;
        try{
            isLock = lock.tryLock(3, TimeUnit.SECONDS);
            log.info("是否获取到锁: {}", isLock);
            if (!isLock) {
                throw new ClientException("请求过多，请稍后再试");
            }
            SerialNumber sn = super.getById(serialNumberTypeEnum.getCode());
            TsfPreconditions.checkArgument(Objects.nonNull(sn),new ServiceException("请联系管理员配置单据编号规则"));
            //前缀
            String docNo = StringUtils.removeSpecialSymbol(sn.getPrefix());
            //日期规则
            TimeRuleEnum timeRuleEnum = TimeRuleEnum.of(sn.getTimeRule());
            if(TimeRuleEnum.NO!=timeRuleEnum){
                docNo = docNo.concat(DateUtils.format(new Date(),timeRuleEnum.getCode()));
            }
            //流水号长度大于零
            if(sn.getSerialLength()>0) {
                LocalDateTime now = LocalDateTime.now();
                //检查是否需要重置流水号
                SerialClearEnum serialClearEnum = SerialClearEnum.of(sn.getSerialClear());
                switch (serialClearEnum) {
                    case YEAR://年
                        DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy");
                        if (!df.format(now).equals(df.format(sn.getDateSerial()))) {
                            sn.setNowSerial(0);
                        }
                        break;
                    case MONTH://月
                        df = DateTimeFormatter.ofPattern("yyyy-MM");
                        if (!df.format(now).equals(df.format(sn.getDateSerial()))) {
                            sn.setNowSerial(0);
                        }
                        break;
                    case DAY://日
                        df = DateTimeFormatter.ofPattern("yyyy-MM-dd");
                        if (!df.format(now).equals(df.format(sn.getDateSerial()))) {
                            sn.setNowSerial(0);
                        }
                        break;
                }
                //流水号增长1
                sn.setNowSerial(sn.getNowSerial() + 1);
                docNo = docNo.concat(autoComplement(sn.getSerialLength(), sn.getNowSerial()));
                //更新流水号生成时间
                sn.setDateSerial(now);
                super.updateById(sn);
            }
            return docNo;
        } catch (InterruptedException e) {
            log.error("获取系统单号获取锁异常",e);
            throw new ClientException("请求过多，请稍后再试");
        } finally {
            //释放锁
            lock.unlock();
        }
    }


    /**=
     * 结果展示
     * @param sn
     * @return
     */
    private String showResult(SerialNumber sn){
        //前缀
        String docNo = StringUtils.removeSpecialSymbol(sn.getPrefix());
        //日期规则
        TimeRuleEnum timeRuleEnum = TimeRuleEnum.of(sn.getTimeRule());
        if(TimeRuleEnum.NO!=timeRuleEnum){
            docNo = docNo.concat(DateUtils.format(new Date(),timeRuleEnum.getCode()));
        }
        //流水号
        docNo = docNo.concat(autoComplement(sn.getSerialLength(),sn.getNowSerial()));
        return docNo;
    }

    /**=
     * 自动补位零
     * @param serialLength
     * @param nowSerial
     * @return
     */
    private String autoComplement(Integer serialLength,Integer nowSerial){
        if(serialLength>0){
            String pattern = "";
            for(int i=0;i<serialLength;i++){
                pattern+="0";
            }
            DecimalFormat df = new DecimalFormat(pattern);
            return df.format(nowSerial);
        }
        return "";
    }
}
